/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

#ifndef __dylab_TetraComplex_h__
#define __dylab_TetraComplex_h__

//===========================================================================================
#include <dylab/utils/geometry/TetrahedronGeometry.h>
#include <dylab/utils/topology/TriComplex.h>
#include <dylab/utils/exceptions/EInvalidArgument.h>
#include <dylab/utils/exceptions/EDoesNotExists.h>

//===========================================================================================
namespace dylab {
            
    /** TetraComplex is 3-simplical _complex.
     */   
    template <
        class _NodeSimplexDataT = EmptyClass,
        typename _EdgeSimplexDataT = EmptyClass,
        typename _TriSimplexDataT = EmptyClass,
        typename _TetraDataT = EmptyClass
    >
    class TetraComplex 
        : public TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>
    {           
    public:
        // only for convenience (and typing ;-)
        typedef typename NodeComplex<_NodeSimplexDataT>                                     NodeComplexT;
        typedef typename EdgeComplex<_NodeSimplexDataT, _EdgeSimplexDataT>                          EdgeComplexT;
        typedef typename TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>                 TriComplexT;
        typedef typename TetraComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT, _TetraDataT>   TetraComplexT;
    public:
        //-----------------------------------------------------------------------------
        /// TetraSimplex is 3-simplex.
        class TetraSimplex 
            : public Simplex
            , public _TetraDataT
        {      
        public:
            //
            static const order_t ORDER = ORDER_3;
        private:
            ///
            nodeSimplexID_t mNodeIDs[TetrahedronGeometry::V_COUNT];
        public:
            /** builds tetrahedron from its 4 apexes (nodes) and generates its id from them
             */
            TetraSimplex(TetraComplexT * _complex, nodeSimplexID_t _n1, nodeSimplexID_t _n2, nodeSimplexID_t _n3, nodeSimplexID_t _n4)
                : Simplex(ORDER, _complex->generateTetraID(_n1, _n2, _n3, _n4), _complex, true, false)
                    { 
                        // store node ids
                        mNodeIDs[TetrahedronGeometry::V_0] = _n1;
                        mNodeIDs[TetrahedronGeometry::V_1] = _n2;
                        mNodeIDs[TetrahedronGeometry::V_2] = _n3;
                        mNodeIDs[TetrahedronGeometry::V_3] = _n4;

                        // attach all tetrahedron triangles, create them if necessary
                        for (TetrahedronGeometry::FaceIdxIterator faceIdx; faceIdx.isValid(); faceIdx++)
                            _attachTriangle(faceIdx, getTriID(faceIdx));

                        // notify that the tetrahedron creation is done
                        getTetraComplex()->_notifySimplexCreated(this); 
                    }
            /**
             */
            virtual ~TetraSimplex()
                    { }
            /** 
             */
            const TetraComplexT * getTetraComplex() const
                    { return dynamicCast<const TetraComplexT>(getComplex()); }
            /** 
             */
            TetraComplexT * getTetraComplex()
                    { return dynamicCast<TetraComplexT>(getComplex()); }
            /** 
             */
            nodeSimplexID_t getNodeID(TetrahedronGeometry::vertexidx_t _vertexIdx) const
                    { return mNodeIDs[_vertexIdx]; }
            /** 
             */
            bool hasNode(nodeSimplexID_t _nodeID) const
                    { 
                        bool found = false;
                        for (TetrahedronGeometry::VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++)
                        {
                            if (getNodeID(vertexIdx) == _nodeID)
                            {
                                found = true;
                                break;
                            }
                        }

                        return found;
                    }
            /** 
             */
            edgeSimplexID_t getEdgeID(TetrahedronGeometry::edgeidx_t _edgeIdx) const
                    { 
                        nodeSimplexID_t edgeNodeIDs[EdgeGeometry3::V_COUNT];
                        getEdgeNodeIDs(_edgeIdx, edgeNodeIDs);
                        return getTetraComplex()->generateEdgeID(edgeNodeIDs[EdgeGeometry3::V_0], edgeNodeIDs[EdgeGeometry3::V_1]);
                    }
            /** 
             */
            void getEdgeNodeIDs(TetrahedronGeometry::edgeidx_t _edgeIdx, nodeSimplexID_t (&_nodeIDs)[EdgeGeometry3::V_COUNT]) const
                    { 
                        TetrahedronGeometry::vertexidx_t edgeVertexIndices[EdgeGeometry3::V_COUNT];
                        TetrahedronGeometry::getEdgeVertexIndices(_edgeIdx, edgeVertexIndices);
                        for (EdgeGeometry3::VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++)
                            _nodeIDs[vertexIdx] = mNodeIDs[edgeVertexIndices[vertexIdx]];
                    }
            /** 
             */
            triSimplexID_t getTriID(TetrahedronGeometry::faceidx_t _faceIdx) const
                    { 
                        nodeSimplexID_t faceNodeIDs[TriangleGeometry3::V_COUNT];
                        getFaceNodeIDs(_faceIdx, faceNodeIDs);
                        return getTetraComplex()->generateTriID(faceNodeIDs[TriangleGeometry3::V_0], faceNodeIDs[TriangleGeometry3::V_1], faceNodeIDs[TriangleGeometry3::V_2]);
                    }
            /** 
             */
            void getFaceNodeIDs(TetrahedronGeometry::faceidx_t _faceIdx, nodeSimplexID_t (&_nodeIDs)[TriangleGeometry3::V_COUNT]) const
                    { 
                        TetrahedronGeometry::vertexidx_t faceVertexIndices[TriangleGeometry3::V_COUNT];
                        TetrahedronGeometry::getFaceVertexIndices(_faceIdx, faceVertexIndices);
                        for (TriangleGeometry3::VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++)
                            _nodeIDs[vertexIdx] = mNodeIDs[faceVertexIndices[vertexIdx]];
                    }
            /**
             */
            void convertTriIDToFaceNodeIDs(triSimplexID_t _triID, nodeSimplexID_t (&_nodeIDs)[TriangleGeometry3::V_COUNT]) const
                    {
                        bool converted = false;
                        for (TetrahedronGeometry::FaceIdxIterator faceIdx; faceIdx.isValid(); faceIdx++)
                        {
                            if (_triID == getTriID(faceIdx))
                            {
                                getFaceNodeIDs(faceIdx, _nodeIDs);
                                converted = true;
                            }
                        }
                        
                        if (!converted) 
                            throw EInvalidArgument(String::fmt("Invalid triangle ID: %1%", (uint32_t)_triID));                        
                    }
            /**
             */
            triSimplexID_t getOppositeTriID(nodeSimplexID_t _nodeID) const
                    {
                        triSimplexID_t triID = 0;
                        nodeSimplexID_t faceNodeIDs[TriangleGeometry3::V_COUNT];
                        
                        // check, if the node is part of the tetrahedron
                        if (!hasNode(_nodeID))
                            throw EDoesNotExists(String::fmt("Tetrahedron doesn't has the node (id: %1%)", (uint32_t)_nodeID));

                        for (TetrahedronGeometry::FaceIdxIterator faceIdx; faceIdx.isValid(); faceIdx++)
                        {
                            bool found = true;
                            getFaceNodeIDs(faceIdx, faceNodeIDs);
                            for (TriangleGeometry3::VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++)
                            {
                                if (faceNodeIDs[vertexIdx] == _nodeID)
                                {
                                    found = false;
                                    break;
                                }
                            }

                            if (found)
                            {
                                triID = getTriID(faceIdx);
                                break;
                            }
                        }

                        if (triID == 0)
                            throw EInvalidArgument(String::fmt("Invalid node id: %1%", (uint32_t)_nodeID));

                        return triID;
                    }

        private:
            /**
             */
            void _attachTriangle(TetrahedronGeometry::faceidx_t _faceIdx, triSimplexID_t _triID)
                    {
                        // store pointer to the parent tetra complex 
                        TetraComplexT * tetraComplex = getTetraComplex();

                        // try to find the triangle in the complex
                        Simplex * simplex = tetraComplex->simplicesReader(TriSimplex::ORDER).find(_triID);

                        // if not present, create new one
                        if (simplex == NULL)
                        {
                            nodeSimplexID_t _n[TriangleGeometry3::V_COUNT];
                            getFaceNodeIDs(_faceIdx, _n);
                            simplex = new TriSimplex(tetraComplex, _n[TriangleGeometry3::V_0], _n[TriangleGeometry3::V_1], _n[TriangleGeometry3::V_2]);
                            DYLAB_ASSERT(simplex != NULL);
                        }

                        // estrablish connection with the triangle
                        connect(simplex);
                    }
        };
        //-----------------------------------------------------------------------------

    public:
        /// builds a map of all tetrahedron's nodes
        class TetraNodes : public SimplexNodes
        {
        public:
            TetraNodes(TetraSimplex * _tetra)
                : SimplexNodes(_tetra)
                    { }
        };
        //-----------------------------------------------------------------------------

    public:
        /// builds a map of all tetrahedron's edges
        class TetraEdges : public SimplexEdges
        {
        public:
            TetraEdges(TetraSimplex * _tetra)
                : SimplexEdges(_tetra)
                    { }
        };
        //-----------------------------------------------------------------------------

    public:
        /// builds a map of all tetrahedron's triangles
        class TetraTris : public SimplexTris
        {
        public:
            TetraTris(TetraSimplex * _tetra)
                : SimplexTris(_tetra)
                    { }
        };
        //-----------------------------------------------------------------------------

    public:
        /// merges a map of all adjacent nodes of the specified _node, _edge, triangle or tetrahedron
        class AdjacentNodes : public TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentNodes
        {
        protected:
            AdjacentNodes()
                    { }
        public:
            AdjacentNodes(NodeSimplex * _node)
                : TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentNodes(_node)
                    { }
            AdjacentNodes(EdgeSimplex * _edge)
                : TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentNodes(_edge)
                    { }
            AdjacentNodes(TriSimplex * _faceIdx)
                : TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentNodes(_edge)
                    { }
            AdjacentNodes(TetraSimplex * _tetra)
                    { 
                        AdjacentEdges adjacentEdges(_tetra);
                        for (Simplices::Iterator it(adjacentEdges.simplicesIterator()); it.isValid(); it++)
                            mergeSubSimplices(*it);
                    }
        };
        //-----------------------------------------------------------------------------

    public:
        /// merges a map of all adjacent edges of the specified _node, _edge, triangle or tetrahedron
        class AdjacentEdges : public TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentEdges
        {
        protected:
            AdjacentEdges()
                    { }
        public:
            AdjacentEdges(NodeSimplex * _node)
                : TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentEdges(_node)
                    { }
            AdjacentEdges(EdgeSimplex * _edge)
                : TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentEdges(_edge)
                    { }
            AdjacentEdges(TriSimplex * _faceIdx)
                : TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentEdges(_faceIdx)
                    { }
            AdjacentEdges(TetraSimplex * _tetra)
                    { 
                        AdjacentTris adjacentTris(_tetra);
                        for (Simplices::Iterator it(adjacentTris.simplicesIterator()); it.isValid(); it++)
                            mergeSimplices(it->getSubSimplices());                        
                    }
        };
        //-----------------------------------------------------------------------------

    public:
        /// merges a map of all adjacent tris of the specified _node, _edge, triangle or tetrahedron
        class AdjacentTris : public TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentTris
        {
        protected:
            AdjacentTris()
                    { }
        public:
            AdjacentTris(NodeSimplex * _node)
                : TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentTris(_node)
                    { }
            AdjacentTris(EdgeSimplex * _edge)
                : TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentTris(_edge)
                    { }
            AdjacentTris(TriSimplex * _faceIdx)
                : TriComplex<_NodeSimplexDataT, _EdgeSimplexDataT, _TriSimplexDataT>::AdjacentTris(_faceIdx)
                    { }
            AdjacentTris(TetraSimplex * _tetra)
                    { mergeSuperAdjacentSimplices(_tetra->getSubSimplices()); }
        };
        //-----------------------------------------------------------------------------

    public:
        /// merges a map of all adjacent tetrahedra of the specified _node, _edge, triangle or tetrahedron
        class AdjacentTetras : public AdjacentSimplices
        {
        protected:
            AdjacentTetras()
                : AdjacentSimplices(TetraSimplex::ORDER)
                    { }
        public:
            AdjacentTetras(NodeSimplex * _node)
                : AdjacentSimplices(TetraSimplex::ORDER)
                    {  
                        for (Simplices::Iterator edge(_node->getSuperSimplices()); edge.isValid(); edge++)
                            mergeAdjacentSuperSimplices(node->getSuperSimplices());
                    }
            AdjacentTetras(EdgeSimplex * _edge)
                : AdjacentSimplices(TetraSimplex::ORDER)
                    { mergeAdjacentSuperSimplices(_edge->getSuperSimplices()); }
            AdjacentTetras(TriSimplex * _faceIdx)
                : AdjacentSimplices(TetraSimplex::ORDER)
                    { mergeAdjacentSuperSimplices(_faceIdx); }
            AdjacentTetras(TetraSimplex * _tetra)
                : AdjacentSimplices(TetraSimplex::ORDER)
                    { mergeSubAdjacentSimplices(_tetra); }
        };
        //-----------------------------------------------------------------------------

	public:
        // only for convenience (and typing ;-)
		typedef typename TetraSimplex::id_t tetraSimplexID_t;

    public:

        /**
        */
        TetraComplex()
                { }

        /**
         */           
        virtual ~TetraComplex()
                { }
        
        /**
         */           
        TetraSimplex * getTetra(tetraSimplexID_t _tetraID)
                {  return dynamicCast<TetraSimplex>(simplicesReader(TetraSimplex::ORDER).get(_tetraID)); }

        /**
         */           
        const TetraSimplex * getTetra(tetraSimplexID_t _tetraID) const
                {  return dynamicCast<const TetraSimplex>(simplicesReader(TetraSimplex::ORDER).get(_tetraID)); }

        /**
         */           
        tetraSimplexID_t generateTetraID()
                { return generateSimplexID(TetraSimplex::ORDER); }

        /**
         */           
        tetraSimplexID_t generateTetraID(nodeSimplexID_t _n1, nodeSimplexID_t _n2, nodeSimplexID_t _n3, nodeSimplexID_t _n4) const
                { return HashGenerator<nodeSimplexID_t>::SortedAndFactorised<>().addValue(_n1, 157).addValue(_n2, 397).addValue(_n3, 557).addValue(_n4, 1129).generate().getHash(); }

        /**
         */           
        void destroyTetra(tetraSimplexID_t _tetraID)
                { destroySimplex(TetraSimplex::ORDER, _tetraID); }
    };
    
}
//===========================================================================================
#endif // __dylab_TetraComplex_h__
